package modules

import (
	"encoding/json"
	"fmt"
	"github.com/go-redis/cache/v8"
	"github.com/go-redis/redis/v8"
	"github.com/go-redsync/redsync/v4"
	"github.com/go-redsync/redsync/v4/redis/goredis/v8"
	"gspeed/bootstrap/global"
	"time"
)

type redisClient struct {
	client  *redis.Client
	cluster *redis.ClusterClient
	cache   *cache.Cache
}

var Redis redisClient

// redis 客户端
func (r *redisClient) NewSingle() *redis.Client {
	if r.client != nil {
		return r.client
	}
	// 设置默认值
	global.Config.SetDefault("redis.networkType", "tcp")
	global.Config.SetDefault("redis.host", "localhost")
	global.Config.SetDefault("redis.port", 6327)
	global.Config.SetDefault("redis.password", "")
	global.Config.SetDefault("redis.db", 0)
	global.Config.SetDefault("redis.pool.min", 100)
	global.Config.SetDefault("redis.pool.max", 100)
	global.Config.SetDefault("redis.pool.wait", true)

	// 建立连接池
	r.client = redis.NewClient(&redis.Options{
		//连接信息
		//网络类型，tcp or unix，默认tcp
		Network: global.Config.GetString("redis.networkType"),
		//主机名+冒号+端口，默认localhost:6379
		Addr:     fmt.Sprintf("%s:%d", global.Config.GetString("redis.host"), global.Config.GetInt("redis.port")),
		Password: global.Config.GetString("redis.password"), //密码
		DB:       global.Config.GetInt("redis.db"),          // redis数据库index

		//连接池容量及闲置连接数量
		PoolSize:     global.Config.GetInt("redis.pool.max"), // 连接池最大socket连接数，默认为4倍CPU数， 4 * runtime.NumCPU
		MinIdleConns: global.Config.GetInt("redis.pool.min"), //在启动阶段创建指定数量的Idle连接，并长期维持idle状态的连接数不少于指定数量；。

		//超时
		DialTimeout:  5 * time.Second, //连接建立超时时间，默认5秒。
		ReadTimeout:  3 * time.Second, //读超时，默认3秒， -1表示取消读超时
		WriteTimeout: 3 * time.Second, //写超时，默认等于读超时
		PoolTimeout:  4 * time.Second, //当所有连接都处在繁忙状态时，客户端等待可用连接的最大等待时长，默认为读超时+1秒。

		//闲置连接检查包括IdleTimeout，MaxConnAge
		IdleCheckFrequency: 60 * time.Second, //闲置连接检查的周期，默认为1分钟，-1表示不做周期性检查，只在客户端获取连接时对闲置连接进行处理。
		IdleTimeout:        5 * time.Minute,  //闲置超时，默认5分钟，-1表示取消闲置超时检查
		MaxConnAge:         0 * time.Second,  //连接存活时长，从创建开始计时，超过指定时长则关闭连接，默认为0，即不关闭存活时长较长的连接

		//命令执行失败时的重试策略
		MaxRetries:      0,                      // 命令执行失败时，最多重试多少次，默认为0即不重试
		MinRetryBackoff: 8 * time.Millisecond,   //每次计算重试间隔时间的下限，默认8毫秒，-1表示取消间隔
		MaxRetryBackoff: 512 * time.Millisecond, //每次计算重试间隔时间的上限，默认512毫秒，-1表示取消间隔

		//可自定义连接函数
		/*Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
			netDialer := &net.Dialer{
				Timeout:   5 * time.Second,
				KeepAlive: 5 * time.Minute,
			}
			return netDialer.Dial("tcp", "127.0.0.1:6379")
		},*/

		//钩子函数
		//仅当客户端执行命令时需要从连接池获取连接时，如果连接池需要新建连接时则会调用此钩子函数
		/*OnConnect: func(ctx context.Context, cn *redis.Conn) error {
			fmt.Printf("conn=%v\n", cn)
			return nil
		},*/

	})
	// 关闭 redis
	// defer r.client.Close()

	return r.client
}

// redis缓存
func (r *redisClient) NewCache() *cache.Cache {
	if r.cache != nil {
		return r.cache
	}
	if r.client == nil {
		r.NewSingle()
	}

	r.cache = cache.New(&cache.Options{
		Redis:      r.client,                            // 实例的redis
		LocalCache: cache.NewTinyLFU(1000, time.Minute), // 支持本地缓存，本地缓存过期时间
		Marshal: func(i interface{}) ([]byte, error) {
			// 自定义 序列化
			return json.Marshal(i)
		},
		Unmarshal: func(bytes []byte, i interface{}) error {
			// 自定义 反序列化
			return json.Unmarshal(bytes, i)
		},
	})
	return r.cache
}

// 分布式锁
func (r *redisClient) NewRedsync() *redsync.Redsync {
	if r.client == nil {
		r.NewSingle()
	}
	// 注意引入的是："github.com/go-redsync/redsync/v4/redis/goredis/v8"
	pool := goredis.NewPool(r.client)
	return redsync.New(pool)
}
